package com.functest.orm

import com.functest.util.OrmUtil
import com.google.common.base.Preconditions
import groovy.transform.ToString

/**
 * @author: DavydovMD
 * Date: 18.04.13
 * Time: 14:23
 */
@ToString
class Condition {
    /**
     * Mapper field name.
     */
    String field
    /**
     * Condition operation
     */
    ConditionOp op
    /**
     *  Supported types: all scalar, GString , Closure, FieldReference.
     *  Closures and GStrings are evaluated during building sql
     */
    def value

    static Condition of(String field, ConditionOp op, def value) {
        new Condition(field: field, op: op, value: value)
    }

    /**
     * Bui
     * @param mapper
     * @return
     */
    GString buildSql(SingleDbTableMapper mapper) {
        def curValue = value

        if (curValue instanceof Closure) {
            curValue = (curValue as Closure).call()
        } else if (curValue instanceof GString) {
            curValue = (curValue as GString).toString()
        }

        //FieldRef is handled in ConditionOp
        if (mapper.isMultiColumn(field)) {
            if (![ConditionOp.EQ, ConditionOp.NEQ].contains(op))
                throw new UnsupportedOperationException("Condition ${this} buildSql failed: $op operator is not compatible with multi-column fields!")

            Map transValues = mapper.getField(field).transformBack(mapper.getField(field), SqlMappingUtil.toCompatible(curValue))

            def fqNames = mapper.getFqSourceNames(field)

            if (transValues.size() == 0) {
                return GString.EMPTY
            }

            GString clause = OrmUtil.asGString('(1=1 ')
            transValues.each { Map.Entry<String, String> transValue ->
                clause = clause.plus(OrmUtil.asGString(' and ' + fqNames.get(transValue.key))).plus(op.buildSql(transValue.value))

            }
            clause = clause.plus(OrmUtil.asGString(' ) '))
            return clause;
        }

        def transValue

        //Special case: Collections

        if ([ConditionOp.IN, ConditionOp.NOT_IN].contains(op)) {
            Preconditions.checkArgument((curValue instanceof Collection), "Only collections are supported as operands for IN/NOT IN, but ${curValue?.class} encountered!", [])
            transValue = (curValue as List).collect { mapper.transformBackFieldValue(field, SqlMappingUtil.toCompatible(it)) }
        } else {
            transValue = mapper.transformBackFieldValue(field, SqlMappingUtil.toCompatible(curValue))
        }
        OrmUtil.asGString(' ').plus(OrmUtil.asGString(mapper.getFqSourceName(field)).toString()).plus(op.buildSql(transValue))


    }

    boolean apply(def left, boolean sqlNullRule = false) {
        op.apply(left, value, sqlNullRule)
    }

    boolean equals(o) {
        if (this.is(o)) return true
        if (!(o instanceof Condition)) return false

        Condition condition = (Condition) o

        if (field != condition.field) return false
        if (op != condition.op) return false
        if (value != condition.value) return false

        true
    }

    int hashCode() {
        int result
        result = (field != null ? field.hashCode() : 0)
        result = 31 * result + (op != null ? op.hashCode() : 0)
        result = 31 * result + (value != null ? value.hashCode() : 0)
        result
    }
}
